<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no">
    <meta name="description" content="Explore node transformations of 3D models.">
    <meta name="cesium-sandcastle-labels" content="Development">
    <title>Cesium Demo</title>
    <script type="text/javascript" src="../Sandcastle-header.js"></script>
    <script type="text/javascript" src="../../../Build/CesiumUnminified/Cesium.js" nomodule></script>
    <script type="module" src="../load-cesium-es6.js"></script>
</head>
<body class="sandcastle-loading" data-sandcastle-bucket="bucket-requirejs.html">
<style>
    @import url(../templates/bucket.css);
    #toolbar {
        background: rgba(42, 42, 42, 0.8);
        padding: 4px;
        border-radius: 4px;
    }
    #toolbar input {
        vertical-align: middle;
        padding-top: 2px;
        padding-bottom: 2px;
    }
</style>
<div id="cesiumContainer" class="fullSize"></div>
<div id="loadingOverlay"><h1>Loading...</h1></div>
<div id="toolbar">
<table><tbody>
    <tr>
        <td colspan="2" data-bind="click: function() { showTranslation = !showTranslation }"><span data-bind="text: showTranslation ? '-' : '+'">+</span> Translation</td>
    </tr>
    <tr data-bind="visible: showTranslation">
        <td>X</td>
        <td>
            <input type="range" min="-3" max="3" step="0.01" data-bind="value: translationX, valueUpdate: 'input'">
            <input type="text" size="2" data-bind="value: translationX">
        </td>
    </tr>
    <tr data-bind="visible: showTranslation">
        <td>Y</td>
        <td>
            <input type="range" min="-3" max="3" step="0.01" data-bind="value: translationY, valueUpdate: 'input'">
            <input type="text" size="2" data-bind="value: translationY">
        </td>
    </tr>
    <tr data-bind="visible: showTranslation">
        <td>Z</td>
        <td>
            <input type="range" min="-3" max="3" step="0.01" data-bind="value: translationZ, valueUpdate: 'input'">
            <input type="text" size="2" data-bind="value: translationZ">
        </td>
    </tr>
    <tr>
        <td colspan="2" data-bind="click: function() { showRotation = !showRotation }"><span data-bind="text: showRotation ? '-' : '+'">+</span> Rotation</td>
    </tr>
    <tr data-bind="visible: showRotation">
        <td>H</td>
        <td>
            <input type="range" min="-3" max="3" step="0.01" data-bind="value: rotationHeading, valueUpdate: 'input'">
            <input type="text" size="2" data-bind="value: rotationHeading">
        </td>
    </tr>
    <tr data-bind="visible: showRotation">
        <td>P</td>
        <td>
            <input type="range" min="-3" max="3" step="0.01" data-bind="value: rotationPitch, valueUpdate: 'input'">
            <input type="text" size="2" data-bind="value: rotationPitch">
        </td>
    </tr>
    <tr data-bind="visible: showRotation">
        <td>R</td>
        <td>
            <input type="range" min="-3" max="3" step="0.01" data-bind="value: rotationRoll, valueUpdate: 'input'">
            <input type="text" size="2" data-bind="value: rotationRoll">
        </td>
    </tr>
    <tr>
        <td colspan="2" data-bind="click: function() { showScale = !showScale }"><span data-bind="text: showScale ? '-' : '+'">+</span> Scale</td>
    </tr>
    <tr data-bind="visible: showScale">
        <td>X</td>
        <td>
            <input type="range" min="0.01" max="3" step="0.01" data-bind="value: scaleX, valueUpdate: 'input'">
            <input type="text" size="2" data-bind="value: scaleX">
        </td>
    </tr>
    <tr data-bind="visible: showScale">
        <td>Y</td>
        <td>
            <input type="range" min="0.01" max="3" step="0.01" data-bind="value: scaleY, valueUpdate: 'input'">
            <input type="text" size="2" data-bind="value: scaleY">
        </td>
    </tr>
    <tr data-bind="visible: showScale">
        <td>Z</td>
        <td>
            <input type="range" min="0.01" max="3" step="0.01" data-bind="value: scaleZ, valueUpdate: 'input'">
            <input type="text" size="2" data-bind="value: scaleZ">
        </td>
    </tr>
</tbody></table>
</div>
<script id="cesium_sandcastle_script">
function startup(Cesium) {
    'use strict';
//Sandcastle_Begin
// this can be changed to any glTF model
var modelUrl = '../../SampleData/models/CesiumMan/Cesium_Man.glb';

var viewModel = {
    nodeName : undefined,
    showTranslation : false,
    showRotation : false,
    showScale : false,
    transformations: {}
};

Cesium.knockout.track(viewModel);

// transformation is a computed property returning the values storage for the current node name
Cesium.knockout.defineProperty(viewModel, 'transformation', function() {
    var transformations = viewModel.transformations;
    var nodeName = viewModel.nodeName;
    if (!Cesium.defined(transformations[nodeName])) {
        transformations[nodeName] = {
            translationX : 0.0,
            translationY : 0.0,
            translationZ : 0.0,
            rotationHeading : 0.0,
            rotationPitch : 0.0,
            rotationRoll : 0.0,
            scaleX : 1.0,
            scaleY : 1.0,
            scaleZ : 1.0
        };
        Cesium.knockout.track(transformations[nodeName]);
    }
    return transformations[nodeName];
});

// these writable computed properties produce individual values for use in the UI
['translationX', 'translationY', 'translationZ', 'rotationHeading', 'rotationPitch', 'rotationRoll', 'scaleX', 'scaleY', 'scaleZ'].forEach(function(p) {
    Cesium.knockout.defineProperty(viewModel, p, {
        get: function() {
            return viewModel.transformation[p];
        },
        set: function(value) {
            // coerce values to number
            viewModel.transformation[p] = +value;
        }
    });
});

// these computed properties return each element of the transform
Cesium.knockout.defineProperty(viewModel, 'translation', function() {
    return new Cesium.Cartesian3(viewModel.translationX, viewModel.translationY, viewModel.translationZ);
});
Cesium.knockout.defineProperty(viewModel, 'rotation', function() {
    var hpr = new Cesium.HeadingPitchRoll(viewModel.rotationHeading, viewModel.rotationPitch, viewModel.rotationRoll);
    return Cesium.Quaternion.fromHeadingPitchRoll(hpr);
});
Cesium.knockout.defineProperty(viewModel, 'scale', function() {
    return new Cesium.Cartesian3(viewModel.scaleX, viewModel.scaleY, viewModel.scaleZ);
});
// this computed property combines the above properties into a single matrix to be applied to the node
Cesium.knockout.defineProperty(viewModel, 'matrix', function() {
    return Cesium.Matrix4.fromTranslationQuaternionRotationScale(viewModel.translation, viewModel.rotation, viewModel.scale);
});

var toolbar = document.getElementById('toolbar');
Cesium.knockout.applyBindings(viewModel, toolbar);

var viewer = new Cesium.Viewer('cesiumContainer');
var scene = viewer.scene;

var height = 250000.0;
var origin = Cesium.Cartesian3.fromDegrees(-123.0744619, 44.0503706, height);
var modelMatrix = Cesium.Transforms.headingPitchRollToFixedFrame(origin, new Cesium.HeadingPitchRoll());

var model = scene.primitives.add(Cesium.Model.fromGltf({
    url : modelUrl,
    modelMatrix : modelMatrix,
    minimumPixelSize : 128
}));

model.readyPromise.then(function(model) {
    var camera = viewer.camera;

    // Zoom to model
    var controller = scene.screenSpaceCameraController;
    var r = 2.0 * Math.max(model.boundingSphere.radius, camera.frustum.near);
    controller.minimumZoomDistance = r * 0.5;

    var center = Cesium.Matrix4.multiplyByPoint(model.modelMatrix, model.boundingSphere.center, new Cesium.Cartesian3());
    var heading = Cesium.Math.toRadians(230.0);
    var pitch = Cesium.Math.toRadians(-20.0);
    camera.lookAt(center, new Cesium.HeadingPitchRange(heading, pitch, r * 2.0));

    // enumerate nodes and add options
    var options = Object.keys(model._runtime.nodesByName).map(function(nodeName) {
       return {
           text : nodeName,
           onselect : function() {
               viewModel.nodeName = nodeName;
           }
       };
    });
    options[0].onselect();
    Sandcastle.addToolbarMenu(options);

    // respond to viewmodel changes by applying the computed matrix
    Cesium.knockout.getObservable(viewModel, 'matrix').subscribe(function(newValue) {
        var node = model.getNode(viewModel.nodeName);
        if (!Cesium.defined(node.originalMatrix)) {
            node.originalMatrix = node.matrix.clone();
        }
        node.matrix = Cesium.Matrix4.multiply(node.originalMatrix, newValue, new Cesium.Matrix4());
    });

}).otherwise(function(error){
    window.alert(error);
});

//Sandcastle_End
Sandcastle.finishedLoading();
}
if (typeof Cesium !== 'undefined') {
    window.startupCalled = true;
    startup(Cesium);
}
</script>
</body>
</html>
